package net.gdface.jmx;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.ExportException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

import javax.management.DynamicMBean;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.JMX;
import javax.management.MBeanInfo;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerInvocationHandler;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;

/**
 * MBean封装基类<br>
 * @author guyadong
 *
 */
public class JMXSupport {
	private static final String NAME_FORMAT="%s:type=%s";
	private static final String JMX_URL_FORMAT="service:jmx:rmi:///jndi/rmi://localhost:%d/jmxrmi";
	private JMXSupport() {
	}
	
	static String formatObjectName(Class<?> clazz){
		return String.format(NAME_FORMAT, clazz.getPackage().getName(),clazz.getSimpleName());
	}
	/**
	 * 返回指定类型的MBean类
	 * @param clazz input class
	 * @param objectNameRef [out]object name
	 * @return MBean interface class
	 * @throws NotCompliantMBeanException clazz不是MBean接口类或没有找到clazz对应的MBean接口类
	 */
	public static Class<?> parseMBeanClass(Class<?> clazz, AtomicReference<String> objectNameRef) 
			throws NotCompliantMBeanException{
		if(clazz == null){
			throw new IllegalArgumentException("clazz is null");
		}
		Class<?>mbeanClass;
		if(JMX.isMXBeanInterface(clazz)){
			mbeanClass = Introspector.getMXBeanInterface(clazz);
		}else{
			try {
				mbeanClass = Introspector.getStandardMBeanInterface(clazz);
			} catch (NotCompliantMBeanException e) {
				if(clazz.isInterface()){
					mbeanClass = clazz;
				}else{
					// clazz 非接口类时尝试通过类名获取对应的MBean,MXBean接口类
					try {
						// 尝试查找 后缀为MBean的接口类
						mbeanClass = Class.forName(clazz.getName() + "MBean");
					} catch (ClassNotFoundException e1) {
						try {
							// 尝试查找 后缀为MXBean的接口类
							mbeanClass = Class.forName(clazz.getName() + "MXBean");
						} catch (ClassNotFoundException e2) {
							throw e;
						}
					}
					// 找到类名匹配的类必须是接口,如果不是抛出异常
					if(!mbeanClass.isInterface()){
						throw e;
					}
				}
			}
		}
		if(objectNameRef != null){
			objectNameRef.set(String.format(NAME_FORMAT, 
					mbeanClass.getPackage().getName(),
					mbeanClass.getSimpleName().replaceAll("MX?Bean$", "")));
		}
		return mbeanClass;
	}
	
	/**
	 * 返回指定类型的MBean类
	 * @param object input object
	 * @param objectNameRef [out]object name
	 * @return MBean interface class
	 * @throws NotCompliantMBeanException clazz不是MBean接口类或没有找到clazz对应的MBean接口类
	 */
	public static Class<?> parseMBeanClass(Object object, AtomicReference<String> objectNameRef) 
			throws NotCompliantMBeanException{
		if(object == null){
			throw new NullPointerException("object is null");
		}
		return parseMBeanClass(object.getClass(), objectNameRef);
	}
	/**
	 * 创建指定类的{@link ObjectName}实例
	 * @param clazz
	 * @return {@link ObjectName}实例
	 * @throws NotCompliantMBeanException
	 */
	public static ObjectName createObjectName(Class<?> clazz) 
			throws NotCompliantMBeanException{
		AtomicReference<String> objectNameRef = new AtomicReference<>();
		parseMBeanClass(clazz, objectNameRef);
		//create object name
		try {
			return new ObjectName(objectNameRef.get());
		} catch (MalformedObjectNameException e) {
			throw new RuntimeException(e);
		}
	}
	public static ObjectName createObjectName(Object mbeanImpl) 
			throws NotCompliantMBeanException{
		if(null == mbeanImpl){
			throw new NullPointerException("mbeanImpl is null");
		}
		Class<?> mbeanClass = mbeanImpl.getClass();
		if(mbeanImpl instanceof DynamicMBean){
			try {
				mbeanClass = Class.forName(((DynamicMBean)mbeanImpl).getMBeanInfo().getClassName());
			} catch (ClassNotFoundException e) {
				throw new NotCompliantMBeanException(e.getMessage());
			}
		}
		return createObjectName(mbeanClass);
	}
	/**
	 * 启动 JMXConnectorServer,如果指定端口已经注册则忽略
	 * @param port Registry  port,use 1099 if null,return if {@code <= 0}
	 * @throws IOException
	 */
	public static void startJmxServer(Integer port) throws 
			IOException{
		if(port == null){
			port = 1099;
		}
		if(port < 0){
			return ;
		}
		//create mbean server
		MBeanServer server = ManagementFactory.getPlatformMBeanServer();         
		try {
			LocateRegistry.createRegistry( port );
		} catch (ExportException e) {
			//System.out.printf("%s:%s\n",e.getClass().getSimpleName(), e.getMessage());
			return;
		}
	
		//构造JMXServiceURL
		JMXServiceURL jmxServiceURL = new JMXServiceURL(String.format(JMX_URL_FORMAT,port));
		//创建JMXConnectorServer
		JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(jmxServiceURL, null, server);  
		//启动
		cs.start();
	}
	/**
	 * 注册MBean实例<br>
	 * 该方法不会启动JMX server
	 * @param mbeanImpl MBean instance
	 * @param objectName object names
	 * @throws InstanceAlreadyExistsException
	 * @throws MBeanRegistrationException
	 * @throws NotCompliantMBeanException
	 */
	public static void registerMBeanOnly(Object mbeanImpl,ObjectName objectName) throws 
	InstanceAlreadyExistsException, 
	MBeanRegistrationException, 
	NotCompliantMBeanException{
		if(mbeanImpl == null){
			throw new IllegalArgumentException("mbeanImpl is null");
		}
		if(objectName == null){
			throw new IllegalArgumentException("objectName is null");
		}
		
		//create mbean server
		MBeanServer server = ManagementFactory.getPlatformMBeanServer();         
		
		//create mbean and register mbean 
		server.registerMBean(mbeanImpl, objectName);
		
	}

	/**
	 * 注册MBean实例<br>
	 * 该方法不会启动JMX server
	 * @param mbeanImpl
	 * @param mbeanClass
	 * @throws InstanceAlreadyExistsException
	 * @throws MBeanRegistrationException
	 * @throws NotCompliantMBeanException
	 */
	public static void registerMBeanOnly(Object mbeanImpl,Class<?> mbeanClass) throws 
	InstanceAlreadyExistsException, 
	MBeanRegistrationException, 
	NotCompliantMBeanException{
		//create object name
		ObjectName objectName = createObjectName(mbeanClass);
		registerMBeanOnly(mbeanImpl,objectName);
	}

	/**
	 * 注册MBean实例<br>
	 * 该方法不会启动JMX server
	 * @param mbeanImpl
	 * @throws InstanceAlreadyExistsException
	 * @throws MBeanRegistrationException
	 * @throws NotCompliantMBeanException
	 */
	public static void registerMBeanOnly(Object mbeanImpl) throws 
			InstanceAlreadyExistsException, 
			MBeanRegistrationException, 
			NotCompliantMBeanException{
		//create object name
		ObjectName objectName = createObjectName(mbeanImpl);
		registerMBeanOnly(mbeanImpl,objectName);
	}

	/**
	 * 注册MBean实例,并启动JMX server
	 * @param mbeanImpl MBean instance
	 * @param objectName object names
	 * @param port Registry  port,use 1099 if null,no call {@link #startJmxServer(Integer)} if {@code <= 0}
	 * @throws InstanceAlreadyExistsException
	 * @throws MBeanRegistrationException
	 * @throws NotCompliantMBeanException
	 * @throws IOException
	 */
	public static void registerMBean(Object mbeanImpl,ObjectName objectName, Integer port) throws 
			InstanceAlreadyExistsException, 
			MBeanRegistrationException, 
			NotCompliantMBeanException, 
			IOException{
		registerMBeanOnly(mbeanImpl,objectName);
		startJmxServer(port);
	}
	/**
	 * 注册MBean实例,并启动JMX server
	 * @param mbeanImpl
	 * @param port Registry  port,use 1099 if null,no call {@link #startJmxServer(Integer)} if {@code <= 0}
	 * @throws InstanceAlreadyExistsException
	 * @throws MBeanRegistrationException
	 * @throws NotCompliantMBeanException
	 * @throws IOException
	 */
	public static void registerMBean(Object mbeanImpl, Integer port) throws 
	InstanceAlreadyExistsException, 
	MBeanRegistrationException, 
	NotCompliantMBeanException, 
	IOException{
		//create object name
		ObjectName objectName = createObjectName(mbeanImpl);
		registerMBean(mbeanImpl,objectName, null);
	}
	/**
	 * 注册MBean实例,并启动JMX server
	 * @param mbeanImpl
	 * @param mbeanClass
	 * @param port Registry  port,use 1099 if null,no call {@link #startJmxServer(Integer)} if {@code <= 0}
	 * @throws InstanceAlreadyExistsException
	 * @throws MBeanRegistrationException
	 * @throws NotCompliantMBeanException
	 * @throws IOException
	 */
	public static void registerMBean(Object mbeanImpl,Class<?> mbeanClass, Integer port) throws 
	InstanceAlreadyExistsException, 
	MBeanRegistrationException, 
	NotCompliantMBeanException, 
	IOException{
		//create object name
		ObjectName objectName = createObjectName(mbeanClass);
		registerMBean(mbeanImpl,objectName, port);
	}

	/**
	 * 获取本机的MBean实例
	 * @param mbeanClass MBean类
	 * @param objectName object name
	 * @param port Registry  port,use 1099 if null
	 * @return T instance
	 * @throws IOException
	 * @throws InstanceNotFoundException 
	 */
	public static <T>T getMBeanInLocalhost(Class<T> mbeanClass,ObjectName objectName, Integer port) 
			throws IOException, InstanceNotFoundException    {
		if(mbeanClass == null){
			throw new IllegalArgumentException("mbeanClass is null");
		}
		if(objectName == null){
			throw new IllegalArgumentException("objectName is null");
		} 
		if(port == null){
			port = 1099;
		}
		JMXServiceURL jmxServiceURL = new JMXServiceURL(String.format(JMX_URL_FORMAT,port));
		JMXConnector jmxc = JMXConnectorFactory.connect( jmxServiceURL,null);

		MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();

		// 检查对象是否存在,不存在则抛出异常
		mbsc.getObjectInstance(objectName);
		return MBeanServerInvocationHandler.
				newProxyInstance(mbsc, objectName, mbeanClass, false);	
	}

	/**
	 * 获取本机的MBean实例
	 * @param mbeanClass MBean类
	 * @param port Registry  port,use 1099 if null
	 * @throws IOException
	 * @throws NotCompliantMBeanException mbeanClass不是MBean接口类或没有找到mbeanClass对应的MBean接口类
	 * @throws InstanceNotFoundException 实例未找到
	 */
	public static <T>T getMBeanInLocalhost(Class<T> mbeanClass, Integer port) 
			throws IOException, NotCompliantMBeanException, InstanceNotFoundException    {
		ObjectName objectName = createObjectName(mbeanClass);
		return getMBeanInLocalhost(mbeanClass,objectName, port);
	}

	@SuppressWarnings("unchecked")
	static <T> Class<? super T> findMXBeanInterface(Class<T> resourceClass) {
	    if (resourceClass == null)
	        throw new IllegalArgumentException("Null resource class");
	    final Set<Class<?>> intfs = transitiveInterfaces(resourceClass);
	    final Set<Class<?>> candidates = new HashSet<>();
	    for (Class<?> intf : intfs) {
	        if (JMX.isMXBeanInterface(intf))
	            candidates.add(intf);
	    }
	reduce:
	    while (candidates.size() > 1) {
	        for (Class<?> intf : candidates) {
	            for (Iterator<Class<?>> it = candidates.iterator(); it.hasNext();
	                ) {
	                final Class<?> intf2 = it.next();
	                if (intf != intf2 && intf2.isAssignableFrom(intf)) {
	                    it.remove();
	                    continue reduce;
	                }
	            }
	        }
	        final String msg =
	            "Class " + resourceClass.getName() + " implements more than " +
	            "one MXBean interface: " + candidates;
	        throw new IllegalArgumentException(msg);
	    }
	    if (candidates.iterator().hasNext()) {
	        return (Class<? super T>) candidates.iterator().next();
	    } else {
	        final String msg =
	            "Class " + resourceClass.getName() +
	            " is not a JMX compliant MXBean";
	        throw new IllegalArgumentException(msg);
	    }
	}

	/* Return all interfaces inherited by this class, directly or
	 * indirectly through the parent class and interfaces.
	 */
	private static Set<Class<?>> transitiveInterfaces(Class<?> c) {
	    Set<Class<?>> set = new HashSet<>();
	    transitiveInterfaces(c, set);
	    return set;
	}

	private static void transitiveInterfaces(Class<?> c, Set<Class<?>> intfs) {
	    if (c == null)
	        return;
	    if (c.isInterface())
	        intfs.add(c);
	    transitiveInterfaces(c.getSuperclass(), intfs);
	    for (Class<?> sup : c.getInterfaces())
	        transitiveInterfaces(sup, intfs);
	}

	@SuppressWarnings("unchecked")
	static <I>Class<I> getMBeanClass(Object delegate){
		try {
			return (Class<I>)parseMBeanClass(delegate, null);
		} catch (NotCompliantMBeanException e) {
			throw new RuntimeException(e);
		}
	}

	@SuppressWarnings("unchecked")
	static <I>Class<I> getMBeanClass(MBeanInfo mBeanInfo){
		try {
			return (Class<I>) Class.forName(mBeanInfo.getClassName());
		} catch (ClassNotFoundException e) {
			throw new RuntimeException(e);
		}
	}
}


